﻿package com.frontalcode
{
	import flash.display.*;
	import flash.filters.*;
	import flash.geom.*;
	import fl.motion.AdjustColor;

	/**
	 * The FrontalSprite extends flash.display.Sprite and is used by all Frontal
	 * visual elements.
	 */
	public class FrontalSprite extends Sprite
	{
		public var docElem : DocumentElement;
		
		// Our registration point.
		//
		public var registrationX : Number = 0;
		public var registrationY : Number = 0;
		
		// Support absolute rotation (absRot property)
		//
		private var _absoluteRotation : Number = 0;
	 
		// Support blur filter properties. Setting blurX and blurY to 0 will
		// unset the filter.
		//
		static private var BLUR_PROPERTIES : Object = 
			{ 
				_blurX:				"blurX",
				_blurY:				"blurY",
				_blurQuality:		"quality"
			 };
		
		private var _blurFilter : BlurFilter;
		private var _blurX : Number = 0;
		private var _blurY : Number = 0;
		private var _blurQuality : int = BitmapFilterQuality.LOW;
		
		// Support drop shadow properties. Setting shadowStrength to 0 will
		// remove the filter. Setting alpha to 0 will remove it. Setting the
		// distance to zero will remove it.
		//
		static private var DROP_SHADOW_PROPERTIES : Object = 
			{ 
				_shadowAlpha:		"alpha",
				_shadowAngle:		"angle",
				_shadowBlurX:		"blurX",
				_shadowBlurY:		"blurY",
				_shadowColor:		"color",
				_shadowDistance:	"distance",
				_shadowInner:		"inner",
				_shadowHideObject:	"hideObject",
				_shadowKnockout:	"knockout",
				_shadowQuality:		"quality",
				_shadowStrength:	"strength"
			 };
		
		private var _dropShadowFilter : DropShadowFilter;
		private var _shadowAlpha : Number = 1;
		private var _shadowAngle : Number = 45;
		private var _shadowBlurX : Number = 4;
		private var _shadowBlurY : Number = 4;
		private var _shadowColor : uint = 0;
		private var _shadowDistance : Number = 0;
		private var _shadowInner : Boolean = false;
		private var _shadowHideObject : Boolean = false;
		private var _shadowKnockout : Boolean = false;
		private var _shadowQuality : int = BitmapFilterQuality.LOW;
		private var _shadowStrength : Number = 0;
		
		// Support glow properties. Setting glowStrength to 0 will
		// remove the filter. So will setting the strength to 0.
		//
		static private var GLOW_PROPERTIES : Object = 
			{ 
				_glowAlpha:		"alpha",
				_glowBlurX:		"blurX",
				_glowBlurY:		"blurY",
				_glowColor:		"color",
				_glowInner:		"inner",
				_glowKnockout:	"knockout",
				_glowQuality:	"quality",
				_glowStrength:	"strength"
			 };
		
		private var _glowFilter : GlowFilter;
		private var _glowAlpha : Number = 1;
		private var _glowBlurX : Number = 4;
		private var _glowBlurY : Number = 4;
		private var _glowColor : uint = 0;
		private var _glowInner : Boolean = false;
		private var _glowKnockout : Boolean = false;
		private var _glowQuality : int = BitmapFilterQuality.LOW;
		private var _glowStrength : Number = 0;
		
		// Support bevel properties. Setting bevelStrength to 0 will
		// remove the filter.
		//
		static private var BEVEL_PROPERTIES : Object = 
			{ 
				_bevelDistance:			"distance",
				_bevelAngle:			"angle",
				_bevelHighlightColor:	"highlightColor",
				_bevelHighlightAlpha:	"highlightAlpha",
				_bevelShadowColor:		"shadowColor",
				_bevelShadowAlpha:		"shadowAlpha",
				_bevelBlurX:			"blurX",
				_bevelBlurY:			"blurY",
				_bevelStrength:			"strength",
				_bevelQuality:			"quality",
				_bevelType:				"type",
				_bevelKnockout:			"knockout"
			 };
		
		private var _bevelFilter : BevelFilter;
		private var _bevelDistance : Number = 4;
		private var _bevelAngle : Number = 45;
		private var _bevelHighlightColor : uint = 0xffffff;
		private var _bevelHighlightAlpha : Number = 1;
		private var _bevelShadowColor : uint = 0;
		private var _bevelShadowAlpha : Number = 1;
		private var _bevelBlurX : Number = 4;
		private var _bevelBlurY : Number = 4;
		private var _bevelStrength : Number = 0;
		private var _bevelQuality : int = 1;
		private var _bevelType : String = "inner";
		private var _bevelKnockout : Boolean = false;
		
		// Support color properties via AdjustColor and ColorMatrixFilter. 
		// Setting all to 0 will remove the filter.
		//
		static private var COLOR_PROPERTIES : Object = 
			{ 
				_saturation:	"saturation",
				_contrast:		"contrast",
				_brightness:	"brightness",
				_hue:			"hue"
			 };

		private var _colorMatrixFilter : ColorMatrixFilter;
		private var _adjustColor : AdjustColor;
		private var _saturation : Number = 0;
		private var _contrast : Number = 0;
		private var _brightness : Number = 0;
		private var _hue : Number = 0;
		
		/**
		 * Creates a new FrontalSprite instance.
		 */
		public function FrontalSprite ( docElem : DocumentElement = null )
		{
			super ( );
			
			this.docElem = docElem;
		}
		
		public function set scale ( val : Number ) : void
		{
			scaleX = val;
			scaleY = val;
		}
		
		public function get scale ( ) : Number { return Math.max ( scaleX, scaleY ); }
		
		// Blur filter support.
		//
		
		public function set blurX ( val : Number ) : void
		{
			_blurX = val;
			onChangeFilterProperties ( "_blurFilter", BlurFilter, BLUR_PROPERTIES );
		}
		
		public function get blurX ( ) : Number { return _blurX; }
	
		public function set blurY ( val : Number ) : void
		{
			_blurY = val;
			onChangeFilterProperties ( "_blurFilter", BlurFilter, BLUR_PROPERTIES );
		}
		
		public function get blurY ( ) : Number { return _blurY; }
		
		public function get blurQuality ( ) : Number { return _blurQuality; }
		
		public function set blurQuality ( val : Number ) : void
		{
			_blurQuality = val;
			onChangeFilterProperties ( "_blurFilter", BlurFilter, BLUR_PROPERTIES );
		}
		
		public function set blur ( val : Number ) : void
		{
			_blurX = val;
			_blurY = val;
			onChangeFilterProperties ( "_blurFilter", BlurFilter, BLUR_PROPERTIES );
		}
		
		public function get blur ( ) : Number { return Math.max ( _blurX, _blurY ); }
		
		// Drop shadow filter support.
		//
		
		public function set shadowAlpha ( val : Number ) : void
		{
			_shadowAlpha = val;
			onChangeFilterProperties ( "_dropShadowFilter", DropShadowFilter, DROP_SHADOW_PROPERTIES );
		}
		
		public function get shadowAlpha ( ) : Number { return _shadowAlpha; }
	
		public function set shadowAngle ( val : Number ) : void
		{
			_shadowAngle = val;
			onChangeFilterProperties ( "_dropShadowFilter", DropShadowFilter, DROP_SHADOW_PROPERTIES );
		}
		
		public function get shadowAngle ( ) : Number { return _shadowAngle; }
	
		public function set shadowBlurX ( val : Number ) : void
		{
			_shadowBlurX = val;
			onChangeFilterProperties ( "_dropShadowFilter", DropShadowFilter, DROP_SHADOW_PROPERTIES );
		}
		
		public function get shadowBlurX ( ) : Number { return _shadowBlurX; }
	
		public function set shadowBlurY ( val : Number ) : void
		{
			_shadowBlurY = val;
			onChangeFilterProperties ( "_dropShadowFilter", DropShadowFilter, DROP_SHADOW_PROPERTIES );
		}
		
		public function get shadowBlurY ( ) : Number { return _shadowBlurY; }
	
		public function set shadowBlur ( val : Number ) : void
		{
			_shadowBlurX = val;
			_shadowBlurY = val;
			onChangeFilterProperties ( "_dropShadowFilter", DropShadowFilter, DROP_SHADOW_PROPERTIES );
		}
		
		public function get shadowBlur ( ) : Number { return Math.max ( _shadowBlurX, _shadowBlurY ); }
		
		public function set shadowColor ( val : Number ) : void
		{
			_shadowColor = val;
			onChangeFilterProperties ( "_dropShadowFilter", DropShadowFilter, DROP_SHADOW_PROPERTIES );
		}
		
		public function get shadowColor ( ) : Number { return _shadowColor; }
	
		public function set shadowDistance ( val : Number ) : void
		{
			_shadowDistance = val;
			onChangeFilterProperties ( "_dropShadowFilter", DropShadowFilter, DROP_SHADOW_PROPERTIES );
		}
		
		public function get shadowDistance ( ) : Number { return _shadowDistance; }
	
		public function set shadowInner ( val : Boolean ) : void
		{
			_shadowInner = val;
			onChangeFilterProperties ( "_dropShadowFilter", DropShadowFilter, DROP_SHADOW_PROPERTIES );
		}
		
		public function get shadowInner ( ) : Boolean { return _shadowInner; }
	
		public function set shadowHideObject ( val : Boolean ) : void
		{
			_shadowHideObject = val;
			onChangeFilterProperties ( "_dropShadowFilter", DropShadowFilter, DROP_SHADOW_PROPERTIES );
		}
		
		public function get shadowHideObject ( ) : Boolean { return _shadowHideObject; }
	
		public function set shadowKnockout ( val : Boolean ) : void
		{
			_shadowKnockout = val;
			onChangeFilterProperties ( "_dropShadowFilter", DropShadowFilter, DROP_SHADOW_PROPERTIES );
		}
		
		public function get shadowKnockout ( ) : Boolean { return _shadowKnockout; }
	
		public function set shadowQuality ( val : Number ) : void
		{
			_shadowQuality = val;
			onChangeFilterProperties ( "_dropShadowFilter", DropShadowFilter, DROP_SHADOW_PROPERTIES );
		}
		
		public function get shadowQuality ( ) : Number { return _shadowQuality; }
	
		public function set shadowStrength ( val : Number ) : void
		{
			_shadowStrength = val;
			onChangeFilterProperties ( "_dropShadowFilter", DropShadowFilter, DROP_SHADOW_PROPERTIES );
		}
		
		public function get shadowStrength ( ) : Number { return _shadowStrength; }
	
		// Glow filter support.
		//
		
		public function set glowAlpha ( val : Number ) : void
		{
			_glowAlpha = val;
			onChangeFilterProperties ( "_glowFilter", GlowFilter, GLOW_PROPERTIES );
		}
		
		public function get glowAlpha ( ) : Number { return _glowAlpha; }
	
		public function set glowBlurX ( val : Number ) : void
		{
			_glowBlurX = val;
			onChangeFilterProperties ( "_glowFilter", GlowFilter, GLOW_PROPERTIES );
		}
		
		public function get glowBlurX ( ) : Number { return _glowBlurX; }
	
		public function set glowBlurY ( val : Number ) : void
		{
			_glowBlurY = val;
			onChangeFilterProperties ( "_glowFilter", GlowFilter, GLOW_PROPERTIES );
		}
		
		public function get glowBlurY ( ) : Number { return _glowBlurY; }
	
		public function set glowBlur ( val : Number ) : void
		{
			_glowBlurX = val;
			_glowBlurY = val;
			onChangeFilterProperties ( "_glowFilter", GlowFilter, GLOW_PROPERTIES );
		}
		
		public function get glowBlur ( ) : Number { return Math.max ( _glowBlurX, _glowBlurY ); }
	
		public function set glowColor ( val : Number ) : void
		{
			_glowColor = val;
			onChangeFilterProperties ( "_glowFilter", GlowFilter, GLOW_PROPERTIES );
		}
		
		public function get glowColor ( ) : Number { return _glowColor; }
	
		public function set glowInner ( val : Boolean ) : void
		{
			_glowInner = val;
			onChangeFilterProperties ( "_glowFilter", GlowFilter, GLOW_PROPERTIES );
		}
		
		public function get glowInner ( ) : Boolean { return _glowInner; }
	
		public function set glowKnockout ( val : Boolean ) : void
		{
			_glowKnockout = val;
			onChangeFilterProperties ( "_glowFilter", GlowFilter, GLOW_PROPERTIES );
		}
		
		public function get glowKnockout ( ) : Boolean { return _glowKnockout; }
	
		public function set glowQuality ( val : Number ) : void
		{
			_glowQuality = val;
			onChangeFilterProperties ( "_glowFilter", GlowFilter, GLOW_PROPERTIES );
		}
		
		public function get glowQuality ( ) : Number { return _glowQuality; }
	
		public function set glowStrength ( val : Number ) : void
		{
			_glowStrength = val;
			onChangeFilterProperties ( "_glowFilter", GlowFilter, GLOW_PROPERTIES );
		}
		
		public function get glowStrength ( ) : Number { return _glowStrength; }
	
		// Bevel filter support.
		//
		
		public function set bevelDistance ( val : Number ) : void
		{
			_bevelDistance = val;
			onChangeFilterProperties ( "_bevelFilter", BevelFilter, BEVEL_PROPERTIES );
		}
		
		public function get bevelDistance ( ) : Number { return _bevelDistance; }
	
		public function set bevelAngle ( val : Number ) : void
		{
			_bevelAngle = val;
			onChangeFilterProperties ( "_bevelFilter", BevelFilter, BEVEL_PROPERTIES );
		}
		
		public function get bevelAngle ( ) : Number { return _bevelAngle; }
	
		public function set bevelHighlightColor ( val : Number ) : void
		{
			_bevelHighlightColor = val;
			onChangeFilterProperties ( "_bevelFilter", BevelFilter, BEVEL_PROPERTIES );
		}
		
		public function get bevelHighlightColor ( ) : Number { return _bevelHighlightColor; }
	
		public function set bevelHighlightAlpha ( val : Number ) : void
		{
			_bevelHighlightAlpha = val;
			onChangeFilterProperties ( "_bevelFilter", BevelFilter, BEVEL_PROPERTIES );
		}
		
		public function get bevelHighlightAlpha ( ) : Number { return _bevelHighlightAlpha; }
	
		public function set bevelShadowColor ( val : Number ) : void
		{
			_bevelShadowColor = val;
			onChangeFilterProperties ( "_bevelFilter", BevelFilter, BEVEL_PROPERTIES );
		}
		
		public function get bevelShadowColor ( ) : Number { return _bevelShadowColor; }
	
		public function set bevelShadowAlpha ( val : Number ) : void
		{
			_bevelShadowAlpha = val;
			onChangeFilterProperties ( "_bevelFilter", BevelFilter, BEVEL_PROPERTIES );
		}
		
		public function get bevelShadowAlpha ( ) : Number { return _bevelShadowAlpha; }
	
		public function set bevelBlurX ( val : Number ) : void
		{
			_bevelBlurX = val;
			onChangeFilterProperties ( "_bevelFilter", BevelFilter, BEVEL_PROPERTIES );
		}
		
		public function get bevelBlurX ( ) : Number { return _bevelBlurX; }
	
		public function set bevelBlurY ( val : Number ) : void
		{
			_bevelBlurY = val;
			onChangeFilterProperties ( "_bevelFilter", BevelFilter, BEVEL_PROPERTIES );
		}
		
		public function get bevelBlurY ( ) : Number { return _bevelBlurY; }
	
		public function set bevelBlur ( val : Number ) : void
		{
			_bevelBlurX = val;
			_bevelBlurY = val;
			onChangeFilterProperties ( "_bevelFilter", BevelFilter, BEVEL_PROPERTIES );
		}
		
		public function get bevelBlur ( ) : Number { return Math.max ( _bevelBlurX, _bevelBlurY ); }
		
		public function set bevelStrength ( val : Number ) : void
		{
			_bevelStrength = val;
			onChangeFilterProperties ( "_bevelFilter", BevelFilter, BEVEL_PROPERTIES );
		}
		
		public function get bevelStrength ( ) : Number { return _bevelStrength; }
	
		public function set bevelQuality ( val : Number ) : void
		{
			_bevelQuality = val;
			onChangeFilterProperties ( "_bevelFilter", BevelFilter, BEVEL_PROPERTIES );
		}
		
		public function get bevelQuality ( ) : Number { return _bevelQuality; }
	
		public function set bevelType ( val : String ) : void
		{
			_bevelType = val;
			onChangeFilterProperties ( "_bevelFilter", BevelFilter, BEVEL_PROPERTIES );
		}
		
		public function get bevelType ( ) : String { return _bevelType; }
	
		public function set bevelKnockout ( val : Boolean ) : void
		{
			_bevelKnockout = val;
			onChangeFilterProperties ( "_bevelFilter", BevelFilter, BEVEL_PROPERTIES );
		}
		
		public function get bevelKnockout ( ) : Boolean { return _bevelKnockout; }
	
		// Color filter support.
		//
		
		public function set saturation ( val : Number ) : void
		{
			_saturation = val;
			onChangeFilterProperties ( "_colorMatrixFilter", ColorMatrixFilter, COLOR_PROPERTIES );
		}
		
		public function get saturation ( ) : Number { return _saturation; }
	
		public function set contrast ( val : Number ) : void
		{
			_contrast = val;
			onChangeFilterProperties ( "_colorMatrixFilter", ColorMatrixFilter, COLOR_PROPERTIES );
		}
		
		public function get contrast ( ) : Number { return _contrast; }
	
		public function set brightness ( val : Number ) : void
		{
			_brightness = val;
			onChangeFilterProperties ( "_colorMatrixFilter", ColorMatrixFilter, COLOR_PROPERTIES );
		}
		
		public function get brightness ( ) : Number { return _brightness; }
	
		public function set hue ( val : Number ) : void
		{
			_hue = val;
			onChangeFilterProperties ( "_colorMatrixFilter", ColorMatrixFilter, COLOR_PROPERTIES );
		}
		
		public function get hue ( ) : Number { return _hue; }
	
		internal function onChangeFilterProperties ( filterMemberName : String, filterClass : Class, filterProperties : Object = null ) : void
		{
			// See if we can clear this filter.
			//
			
			var clearFilter : Boolean = false;
			
			if ( filterClass == BlurFilter )
			{
				clearFilter = _blurX == 0 && _blurY == 0;
			}
			else if ( filterClass == DropShadowFilter )
			{
				clearFilter = _shadowAlpha == 0 || _shadowDistance == 0 || _shadowStrength == 0;
			}
			else if ( filterClass == GlowFilter )
			{
				clearFilter = _glowAlpha == 0 || _glowStrength == 0;
			}
			else if ( filterClass == BevelFilter )
			{
				clearFilter = _bevelStrength == 0;
			}
			else if ( filterClass == ColorMatrixFilter )
			{
				clearFilter = _saturation == 0 && _contrast == 0 && _brightness == 0 && _hue == 0;
			}
			
			// First, always remove the filter. This is because Flash won't process
			// the change unless the filters array changes.
			//
			var filtersCopy : Array = filters;
			
			if ( this [ filterMemberName ] != null )
			{
				for ( var i : uint = 0; i < filtersCopy.length; i++ )
				{
					// XXX Object comparison doesn't work here for some reason
					// so we look for _any_ blur filter.
					//
					if ( filtersCopy [ i ] is filterClass )
					{
						filtersCopy.splice ( i, 1 );
						
						if ( filtersCopy.length == 0 )
						{
							filtersCopy = null;
						}
						break;
					}
				}
				
				if ( clearFilter ) this [ filterMemberName ] = null;
			}
			else
			{
				if ( ! clearFilter ) this [ filterMemberName ] = new filterClass ( );
			}
			
			if ( this [ filterMemberName ] != null )
			{
				for ( var property : String in filterProperties )
				{
					if ( filterClass == ColorMatrixFilter )
					{
						if ( _adjustColor == null ) _adjustColor = new AdjustColor ( );
						_adjustColor [ filterProperties [ property ] ] = this [ property ];
					}
					else
					{
						this [ filterMemberName ] [ filterProperties [ property ] ] = this [ property ];
					}
				}
				
				if ( filterClass == ColorMatrixFilter ) this [ filterMemberName ].matrix = _adjustColor.CalculateFinalFlatArray ( );
				if ( filtersCopy == null ) filtersCopy = [ ];
				filtersCopy.push ( this [ filterMemberName ] );
			}
			
			filters = filtersCopy;
		}
		
		// Support for scaling and rotating around an arbitrary registration point.
		//
		
		internal function get coordParent ( ) : DisplayObject { return parent == null ? this : parent; }
		
		private var regOffsets : Object = { };
		private var regCombinedOffsets : Point = new Point ( );
		
		internal function setPropertyWithRegistration ( prop : String, value : Number ) : void
		{
			if ( super [ prop ] != value || regOffsets [ prop ] == null || regOffsets [ prop ].regX != registrationX || regOffsets [ prop ].regY != registrationY || regOffsets [ prop ].value != value )
			{
				var regPt : Point = new Point ( registrationX, registrationY );
				var newRegPt : Point = regPt.clone ( );
				regPt = localToGlobal ( regPt );
				regPt = coordParent.globalToLocal ( regPt );
				super [ prop ] = value;
				newRegPt = localToGlobal ( newRegPt );
				newRegPt = coordParent.globalToLocal ( newRegPt );
				newRegPt = newRegPt.subtract ( regPt );
				super.x = super.x - newRegPt.x;
				super.y = super.y - newRegPt.y;
				var prevOffset : Point = regOffsets [ prop ] == null ? new Point ( ) : regOffsets [ prop ].offset;
				regOffsets [ prop ] = { offset: newRegPt.add ( prevOffset ), value: value, regX: registrationX, regY: registrationY };
				regCombinedOffsets.x = regCombinedOffsets.y = 0;
				for ( var offset : String in regOffsets ) regCombinedOffsets = regCombinedOffsets.add ( regOffsets [ offset ].offset );
			}
		}

		override public function set scaleX   ( value : Number ) : void { setPropertyWithRegistration ( "scaleX",   value ); }
		override public function set scaleY   ( value : Number ) : void { setPropertyWithRegistration ( "scaleY",   value ); }
		override public function set rotation ( value : Number ) : void 
		{ 
			setPropertyWithRegistration ( "rotation", value ); 
			_absoluteRotation = value;
		}
		
		override public function set x ( value : Number ) : void { super.x = value - regCombinedOffsets.x; }
		override public function get x ( ) : Number { return super.x + regCombinedOffsets.x; }
		
		override public function set y ( value : Number ) : void { super.y = value - regCombinedOffsets.y; }
		override public function get y ( ) : Number { return super.y + regCombinedOffsets.y; }
		
		// XXX These aren't working. To work properly, we need to put the
		// registration point through all of the movies transformations and then
		// subtract from the super.mouseX and super.mouseY values. I think.
		//
		// override public function get mouseX ( ) : Number { return super.mouseX + regCombinedOffsets.x - registrationX; }
		// override public function get mouseY ( ) : Number { return super.mouseY + regCombinedOffsets.y - registrationY; }
		
		public function set absRot ( value : Number ) : void
		{
			super.rotation = value;
			_absoluteRotation = value;
		}
		
		public function get absRot ( ) : Number { 	return _absoluteRotation; }
	}
}
